package top.codedance.iotp.slave.service.slave;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.slave.SlaveApplication;
import top.codedance.iotp.slave.bean.SlaveConnectParam;
import top.codedance.iotp.slave.service.slave.holder.RtuDataHolder;
import top.codedance.iotp.slave.service.slave.listenner.RtuListener;
import top.codedance.iotp.slave.service.util.DataConverter;
import com.intelligt.modbus.jlibmodbus.Modbus;
import com.intelligt.modbus.jlibmodbus.data.ModbusCoils;
import com.intelligt.modbus.jlibmodbus.data.ModbusHoldingRegisters;
import com.intelligt.modbus.jlibmodbus.serial.SerialParameters;
import com.intelligt.modbus.jlibmodbus.serial.SerialPort;
import com.intelligt.modbus.jlibmodbus.serial.SerialPortFactoryPJC;
import com.intelligt.modbus.jlibmodbus.serial.SerialUtils;
import com.intelligt.modbus.jlibmodbus.slave.ModbusSlave;
import com.intelligt.modbus.jlibmodbus.slave.ModbusSlaveFactory;
import com.intelligt.modbus.jlibmodbus.utils.SerialPortInfo;
import org.apache.log4j.Logger;

import java.util.Observable;

public class RtuSlave extends ModbusAbstractAdapter {
    private Logger logger = SlaveApplication.getLogger(RtuSlave.class);
    private ModbusSlave slave = null;
    private SerialParameters sp = null;
    private ModbusHoldingRegisters holdingRegisters = null;
    private ModbusCoils coils = null;

    public RtuSlave(SlaveConnectParam scp) {
        super(scp);
        Modbus.setLogLevel(SlaveApplication.getModbusLogLevel());
    }

    @Override
    public void init() {
        connect();
        long time = System.currentTimeMillis();
        while (true) {
            if (!isOpen()) {
                try {
                    connect();
                }catch (Exception ex){
                    logger.info(ex.getMessage());
                }
                logger.info("Rtu slave is reboot!");
            }else{
                if(System.currentTimeMillis() - time > 1000 * 3600 * 2){
                    destory();
                    try {
                        connect();
                    }catch (Exception ex){
                        logger.info(ex.getMessage());
                    }
                    time = System.currentTimeMillis();
                }
            }
            try {
                Thread.sleep(3000);
            } catch (Exception ex) { }
        }
    }

    @Override
    public void connect() {
        try {
            sp = new SerialParameters();
            sp.setParity(SerialPort.Parity.getParity(param.getParity()));
            sp.setStopBits(param.getStopBits());
            sp.setDataBits(param.getDataBits());
            sp.setBaudRate(SerialPort.BaudRate.getBaudRate(param.getBaudRate()));
            sp.setDevice(param.getComName());
            SerialUtils.setSerialPortFactory(new SerialPortFactoryPJC());
            slave = ModbusSlaveFactory.createModbusSlaveRTU(sp);
            slave.setServerAddress(param.getId());
            slave.setBroadcastEnabled(true);
            slave.setReadTimeout(10000);
            slave.addObserver((Observable o, Object arg) -> {
                SerialPortInfo spi = (SerialPortInfo) arg;
                RtuListener rtuListener = new RtuListener();
                if (spi.isOpened()) {
                    slave.addListener(rtuListener);
                } else {
                    slave.removeListener(rtuListener);
                }
            });
            RtuDataHolder rtuDataHolder = new RtuDataHolder();
            if(SlaveApplication.dataParams.containsKey(REGISTER_NAME)) {
                Integer[] registersAddress = SlaveApplication.dataParams.get(REGISTER_NAME);
                if (registersAddress.length > 0) {
                    holdingRegisters = new ModbusHoldingRegisters(registersAddress.length);
                    rtuDataHolder.setHoldingRegisters(holdingRegisters);
                }
            }
            if(SlaveApplication.dataParams.containsKey(COIL_NAME)) {
                Integer[] coilsAddress = SlaveApplication.dataParams.get(COIL_NAME);
                if (coilsAddress.length > 0) {
                    coils = new ModbusCoils(coilsAddress.length);
                    rtuDataHolder.setCoils(coils);
                }
            }
            slave.setDataHolder(rtuDataHolder);
            Modbus.setAutoIncrementTransactionId(true);
            slave.listen();
            if (slave.isListening()) {
                logger.info("Rtu slave running!");
                Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                    synchronized (slave) {
                        slave.notifyAll();
                    }
                }));

                synchronized (slave) {
                    slave.wait();
                }
                slave.shutdown();
            }else{
                logger.info("Tcp slave dead!");
            }
        }catch (Exception e){
            e.printStackTrace();
            logger.debug(e.getMessage());
        }
    }

    @Override
    public boolean isOpen() {
        return slave.isListening();
    }

    @Override
    public void destory() {
        if(slave != null) {
            try {
                slave.shutdown();
            } catch (Exception e) {
                logger.debug(e.getMessage());
            }
        }
    }

    @Override
    public void send(byte[] data) {
        try {
            JSONObject jo = JSON.parseObject(new String(data));
            int function = jo.getIntValue("function");
            int offset = jo.getIntValue("offset");
            int datatype = jo.getIntValue("datatype");
            switch (function){
                case 16:
                    if(holdingRegisters != null) {
                        DataConverter.modbusConvert(holdingRegisters, datatype, offset, jo);
                    }
                    break;
                case 15:
                    if(coils != null) {
                        coils.set(offset, jo.getBoolean("value"));
                    }
                    break;
                default:
                    break;
            }
        }catch (Exception e){
            logger.debug(e.getMessage());
        }
    }
}
